/**
 * Copyright (C) 2010 Cloudfarming <info@cloudfarming.nl>
 *
 * Licensed under the Eclipse Public License - v 1.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.eclipse.org/legal/epl-v10.html
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package nl.cloudfarming.client.geoviewer.jxmap.panels;

import java.awt.EventQueue;
import java.awt.Rectangle;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.swing.JComponent;
import javax.swing.SwingUtilities;
import nl.cloudfarming.client.geoviewer.Layer;
import nl.cloudfarming.client.geoviewer.LayerEventKey;
import nl.cloudfarming.client.geoviewer.LayerInfo;
import nl.cloudfarming.client.geoviewer.LayerObject;
import nl.cloudfarming.client.geoviewer.LayerObjectNode;
import nl.cloudfarming.client.geoviewer.jxmap.MouseEventForwarder;
import nl.cloudfarming.eventbus.BaseGuiEventProducer;
import nl.cloudfarming.eventbus.GuiEvent;
import nl.cloudfarming.eventbus.GuiEventKey;
import org.jdesktop.swingx.JXMapViewer;
import org.netbeans.api.visual.model.ObjectScene;
import org.netbeans.api.visual.model.ObjectSceneEvent;
import org.netbeans.api.visual.model.ObjectSceneEventType;
import org.netbeans.api.visual.model.ObjectSceneListener;
import org.netbeans.api.visual.model.ObjectState;
import org.netbeans.api.visual.widget.LayerWidget;
import org.netbeans.api.visual.widget.Widget;

/**
 * The WidgetsPanel is used for holding the dynamic map objects, so the user
 * can interact with them, for example showing a tooltip when hover over the
 * object or using a right mouse click context menu (to for example identify
 * the object).
 * 
 * @author Timon Veenstra
 */
public class LayerPanel extends LayerInfo {

    //TODO consider using ExplorerManager to manage nodes in a layer
//    public class YourComponent extends JPanel
//implements ExplorerManager.Provider, Lookup.Provider {
//    private ExplorerManager manager;
//    private Lookup lookup;    public YourComponent() {
//        // same as before...
//        manager = new ExplorerManager();
//        ActionMap map = getActionMap();
//        map.put(DefaultEditorKit.copyAction, ExplorerUtils.actionCopy(manager));
//        map.put(DefaultEditorKit.cutAction, ExplorerUtils.actionCut(manager));
//        map.put(DefaultEditorKit.pasteAction, ExplorerUtils.actionPaste(manager));
//        map.put("delete", ExplorerUtils.actionDelete(manager, true)); // or false
//        // ...but add e.g.:        InputMap keys = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
//        keys.put(KeyStroke.getKeyStroke("control C"), DefaultEditorKit.copyAction);
//        keys.put(KeyStroke.getKeyStroke("control X"), DefaultEditorKit.cutAction);
//        keys.put(KeyStroke.getKeyStroke("control V"), DefaultEditorKit.pasteAction);
//        keys.put(KeyStroke.getKeyStroke("DELETE"), "delete");
//
//        // ...and initialization of lookup variable
//        lookup = ExplorerUtils.createLookup (manager, map);
//    }
//    // ...method as before and getLookup
//    public ExplorerManager getExplorerManager() {
//        return manager;
//    }
//    public Lookup getLookup() {
//        return lookup;
//    }
//    // ...methods as before, but replace componentActivated and    // componentDeactivated with e.g.:    public void addNotify() {
//        super.addNotify();
//        ExplorerUtils.activateActions(manager, true);
//    }
//    public void removeNotify() {
//        ExplorerUtils.activateActions(manager, false);
//        super.removeNotify();
//    }
//}
    private final ObjectScene scene = new ObjectScene();
    private final JComponent view = scene.createView();
    private final LayerWidget mainLayer = new LayerWidget(scene);
    private MouseEventForwarder mouseEventForwarder;
    private final JXMapViewer mapViewer;
    private final RootMapPanelController controller;
    private final Layer layer;

    public LayerPanel(final RootMapPanelController controller, final JComponent forwardEventsTo, Layer layer) {
        this.layer = layer;
        this.mapViewer = controller.getViewer();
        this.controller = controller;
        this.scene.setOpaque(false);
        this.scene.addChild(this.mainLayer);
        this.scene.setMaximumBounds(new Rectangle(0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE));

        if (forwardEventsTo != null) {
            this.mouseEventForwarder = new MouseEventForwarder(this.scene, forwardEventsTo);
            this.view.addMouseListener(this.mouseEventForwarder);
            this.view.addMouseMotionListener(this.mouseEventForwarder);
            this.view.addMouseWheelListener(this.mouseEventForwarder);
        }

        this.scene.addObjectSceneListener(new SelectionChangedListener(), ObjectSceneEventType.OBJECT_SELECTION_CHANGED);

    }

    public JComponent getView() {
        return this.view;
    }

    /**
     * remove objects
     * if remove is called from the event dispatch thread it ill be
     * executed immediate, if not it will be scheduled on the dispatch thread.
     *
     * @param objects
     */
    public void removeObjects(final List<LayerObject<?>> objects) {
        if (EventQueue.isDispatchThread()) {
            internalRemoveObjects(objects);
        } else {
            EventQueue.invokeLater(new Runnable() {

                @Override
                public void run() {
                    internalRemoveObjects(objects);
                }
            });
        }
    }

    private void internalRemoveObjects(List<LayerObject<?>> objects) {
        assert objects != null;
        for (LayerObject layerObject : objects) {
            List<Widget> widgets = this.scene.findWidgets(layerObject);
            if (widgets != null) {
                for (Widget w : widgets) {
                    mainLayer.removeChild(w);
                }
                this.scene.removeObject(layerObject);
            }

        }
        this.scene.validate();
    }

    /**
     * add objects
     * if called from the event dispatch thread it ill be
     * executed immediate, if not it will be scheduled on the dispatch thread.
     *
     * @param objects
     */
    public void addObjects(final List<LayerObject<?>> objects) {
        if (SwingUtilities.isEventDispatchThread()) {
            internalAddObjects(objects);
        } else {
            SwingUtilities.invokeLater(new Runnable() {

                @Override
                public void run() {
                    internalAddObjects(objects);
                }
            });
        }
    }

    private void internalAddObjects(List<LayerObject<?>> objects) {

        for (LayerObject layerObject : objects) {
            addObject(layerObject);
        }
        scene.validate();
    }

    private void addObject(LayerObject object) {

        LayerObjectWidget widget = new LayerObjectWidget(this.scene, object, this.mapViewer, this.layer);

        widget.getActions().addAction(scene.createObjectHoverAction());
        widget.getActions().addAction(scene.createSelectAction());

        //TODO: popup menu for widgets does not show and blocks event to map
//        PopupMenuProvider mp = new PopupMenuProvider() {
//
//            @Override
//            public JPopupMenu getPopupMenu(Widget widget, Point localLocation) {
//                JPopupMenu menu = new JPopupMenu();
//                for (Action a:layer.getActions()){
//                    menu.add(a);
//                }
//                return menu;
//            }
//        };
//        widget.getActions().addAction(ActionFactory.createPopupMenuAction(mp));


        this.mainLayer.addChild(widget);
        LayerObjectNode node = new LayerObjectNode(controller, layer, object);
        this.scene.addObject(node, widget);

    }

    public boolean isInterActive() {
        return this.layer.isInterActive();
    }

    public void setSelectedNode(LayerObjectNode layerObjectNode) {
        Set<LayerObjectNode> selectedNodes = new HashSet<LayerObjectNode>();
        selectedNodes.add(layerObjectNode);
        this.scene.setSelectedObjects(selectedNodes);
        this.scene.getView().repaint();
    }

    /**
     * selection change listener, listens to changes in the selection on the object scene.
     *
     * changes in the selection will be send as gui events
     *
     */
    private class SelectionChangedListener extends BaseGuiEventProducer implements ObjectSceneListener {

        final Set<LayerObjectNode> layerNodes = new HashSet<LayerObjectNode>();

        public SelectionChangedListener() {
            super(LayerEventKey.class);
        }

        private void triggerEvent() {
            GuiEvent<Set<LayerObjectNode>> guiEvent = new GuiEvent<Set<LayerObjectNode>>() {

                @Override
                public GuiEventKey getKey() {
                    return LayerEventKey.NEW_PLOT_SELECTION;
                }

                @Override
                public Set<LayerObjectNode> getContent() {
                    return layerNodes;
                }
            };
            triggerEvent(guiEvent);
        }

        @Override
        public void objectAdded(ObjectSceneEvent event, Object addedObject) {
            if (addedObject instanceof LayerObjectNode) {
                layerNodes.add((LayerObjectNode) addedObject);
                triggerEvent();
            }
        }

        @Override
        public void objectRemoved(ObjectSceneEvent event, Object removedObject) {
            if (removedObject instanceof LayerObjectNode && layerNodes.contains(removedObject)) {
                layerNodes.remove((LayerObjectNode) removedObject);
                triggerEvent();
            }
        }

        @Override
        public void objectStateChanged(ObjectSceneEvent event, Object changedObject, ObjectState previousState, ObjectState newState) {
        }

        @Override
        public void selectionChanged(ObjectSceneEvent event, Set<Object> previousSelection, final Set<Object> newSelection) {
            layerNodes.clear();
            for (Object o : newSelection) {
                if (o instanceof LayerObjectNode) {
                    layerNodes.add((LayerObjectNode) o);
                }
            }
            triggerEvent();
        }

        @Override
        public void highlightingChanged(ObjectSceneEvent event, Set<Object> previousHighlighting, Set<Object> newHighlighting) {
        }

        @Override
        public void hoverChanged(ObjectSceneEvent event, Object previousHoveredObject, Object newHoveredObject) {
        }

        @Override
        public void focusChanged(ObjectSceneEvent event, Object previousFocusedObject, Object newFocusedObject) {
        }
    }
}
